//指针
//1️⃣理解
#include <stdio.h>
int main()
{
    int a=5;
    //printf("%p\n",&a);//打印地址：000000000061FE3C (4个地址中的第一个地址)
    int* p=&a;//把a的地址存起来（存起来的也是a所占内存空间的起始位置，即第一个地址）
    //p就是指针变量
    *p=20;//*解引用操作符，通过p中存放的地址，找到p所指向的对象；*p就是p指向的对象
    //*p=a,a=20;
    printf("%d\n",a);//20  

    printf("%zu\n",sizeof(char*));     //8
    printf("%zu\n",sizeof(short*));    //8
    printf("%zu\n",sizeof(int*));      //8
    printf("%zu\n",sizeof(float*));    //8
    printf("%zu\n",sizeof(double*));   //8
//不管什么类型的指针，都是在创建指针变量，指针变量是用来存放地址的
//指针变量的大小取决于一个地址（即一串数字）存放的时候需要多大的空间
//32位机器上的地址：32bit位 - 4byte 所以指针变量的大小是4个字节
//64位机器上的地址：64bit位 - 8byte 所以指针变量的大小是8个字节
    return 0;
}
//int *p1,*p2,*p3;  p1,p2,p3才都是指针变量
//int *p1,p2,p3;    p1是指针变量，p2、p3是整型变量
//int* p;  int *p;  这两种写法都可以

//2️⃣指针类型的意义    见vs2022 test2
//指针类型决定访问变量内存大小的权限，所以为了完全并合适的访问一个变量的全部空间，就要用适合的指针类型
//int* 4字节   char* 1字节   double* 8字节 ……
//指针变量只是让我们知道了一个变量的首地址，找到了这个变量的访问开端
//但我们无法知道这个变量所占的所有内存空间到底多大，而指针类型就是在说明这个变量所占的总空间，让我们访问了所有内容
//比如，指针变量告诉我们门牌号是多少，我们来到了房间门前，而这个房间到底是50平还是100平是需要合适的指针类型告诉我们

//void*指针   P90
//可以理解为无具体类型的指针，或者叫泛型指针。这种类型的指针可以用来接收任意类型地址。
int main()
{
    int a=10;
    void* p=&a;
    printf("%p\n",p);
    char c='s';
    p=&c;
    printf("%p",p);
    return 0;
}
//输出：
//000000000061FE34
//000000000061FE33
//但也有局限性，void*类型的指针不能直接进行指针的+-整数和解引用运算。
//一般void*类型的指针是使用在函数参数的部分，用来接收不同类型数据的地址，这样设计可以实现泛型编程的效果，即一个函数可以来处理多种类型的数据。

//3️⃣野指针
//①指针变量未初始化
int main()
{
    int *p; 
    //p没有初始化，意味着没有明确的指向，放的是随机值：0xcccccccc
    //随机值的地址不属于我们的程序
    *p=10;//非法访问内存，这里的p就是野指针
    return 0;
} 
//②指针越界访问
int main()
{
    int arr[10]={0};
    int *p=arr;//arr是数组首元素地址，相当于&arr[0]
    int i;
    for(i=0;i<11;i++)  
    {
        *p++=i; //后置++优先级大于*，但因为后置++使用规则是先使用后++
        //所以相当于 *p=i;  p++;   
        //i是0~10，循环11次
        //第十次结束时p++,此时p指向arr[9]后面的地址，相当于arr[10]
        //第十一次进来时，*p=10相当于将arr[10]赋值10，但这不是数组中的空间
        //属于越界访问，此时p为野指针
    }
    return 0;
}
//③指针指向的空间释放
int* test()
{
    int a=10;   //a的作用域在这个{}内部，比如地址为0x0012ff40
    return &a;
}
int main()
{
    int *p=test(); //此时p中存储0x0012ff40这个地址，说明p有能力找到这块空间
    *p=20; //但我们无法访问和操作这个地址中的内容，这里的p是野指针
    //a进作用域创建，出作用域销毁，a的空间还给操作系统了，a的空间已经不属于我们的程序了
    //虽然这个地址还在，里面存储的10也可能还在(比如未被使用,未被覆盖)，但我们没有访问0x0012ff40这个地址内容的权限了
    return 0;
}
//避免野指针，但有局限性
int main()
{
    //初始化
    int* p=NULL; //NULL本质是0
    //*p=10; 这样写是错的，因为0地址无法访问
    //使用指针时
    if(p!=NULL) //或者写成if(p)
    {
        *p=10;
    }
    return 0;
}

//4️⃣指针运算
//①指针+-整数
#define N 5
float values[N];
float *vp; //未初始化，但下面初始化了
for(vp=&values[0];vp<&values[N];) //指针比较大小是指针的关系运算
{
    *vp++=0;//后置++优先级大于*，但因为后置++使用规则是先使用后++
    //*vp=0; vp++;
}
//这个函数是将values数组元素全都初始化为0；
//*vp++和*(vp++)和(*vp)++的区别：
//*vp++等价于*(vp++):都是先取指针指向的内容或说成先解引用，再指针变量自增即地址+1
//(*vp)++:是让指针指向的内容自增
//应用：让数组中所有元素都变成1
int main()
{
    int arr[10]={0};
    int i;
    int sz=sizeof(arr)/sizeof(arr[0]);
    //第一种写法
    for(i=0;i<sz;i++)
    {
        arr[i]=1;
    }
    //第二种写法
    int *p=arr;
    for(i=0;i<sz;i++)
    {
        *p++=1;  //这种改变了p本身的值
        //*p=1;
        //p++;
    }
    //第三种写法
    for(i=0;i<sz;i++)
    {
        *(p+i)=1; //这种p本身没变
    }
    return 0;
}
//②指针-指针
int main()
{
    int arr[10]={0};
    printf("%d\n",&arr[9]-&arr[0]);//输出：9
    printf("%d\n",&arr[0]-&arr[9]);//输出：-9
    return 0;
}//|指针-指针|是指针和指针之间元素的个数
//不是所有指针都能相减，指向同一空间的两个指针才能相减
int main()   //见vs2022 test2
{            //这个程序结果不可预测，虽然能输出但不对，能输出是因为恰好连续分配内存
    int arr[5]={0};
    int brr[5]={0};
    printf("%d\n",&arr[4]-&brr[0]);//输出：12
    printf("%#x\n",&brr[0]);//0x61fe00
    printf("%#x\n",&brr[4]);//0x61fe10
                            //0x61fe14
                            //0x61fe18
                            //0x61fe1c
    printf("%#x\n",&arr[0]);//0x61fe20
    printf("%#x\n",&arr[4]);//0x61fe30
    return 0;
}//这个结果12=brr数组中[0][1][2][3][4]+brr数组和arr数组之间3个元素+arr数组中[0][1][2][3]=5+3+4=12
/*这样写会报错，无法运行程序
int main()
{
    int arr[5]={0};
    char brr[5]={0};
    printf("%d\n",&arr[4]-&brr[0]);
    printf("%d\n",&brr[0]);  6422043
    printf("%d\n",&brr[4]);  6422047
    printf("%d\n",&arr[0]);  6422048
    printf("%d\n",&arr[4]);  6422064
    return 0;
}*/
//应用：求字符串长度 见15.c P60~61
int my_strlen(char *str) //接收字符串首字符首地址,str储存字符a的地址
{
    char* start=str;//把字符a的地址赋值给start中储存起来
    while(*str!='\0')
    {
        str++;  //当str指向'\0'时，跳出循环
    }
    return (str-start); //指向'\0'的指针-字符串首字符首地址
}
int main()
{
    int len=my_strlen("abcdef");
    printf("%d\n",len);
    return 0;
}
//指针+指针？无意义P86
//③指针的关系运算
#define N 5
float values[N];
float *vp; 
for(vp=&values[N];vp>&values[0];)
{
    *--vp=0;//前置--优先级高于*，前置--使用规则是先--后使用
    //vp--;
    //*vp=0;
}
//第一步：vp=&values[5]-1=&values[4] *vp=0
//第二步：vp=&values[4]-1=&values[3] *vp=0
//第三步：vp=&values[3]-1=&values[2] *vp=0
//第四步：vp=&values[2]-1=&values[1] *vp=0
//第五步：vp=&values[1]-1=&values[0] *vp=0
//第六步：vp=&values[0] 跳出循环     vp最终指向values[0]
//简化：
#define N 5
float values[N];
float *vp; 
for(vp=&values[N-1];vp>=&values[0];vp--)
{
    *vp=0;
}
//第一步：vp=&values[4] *vp=0
//第二步：vp=&values[4]-1=&values[3] *vp=0
//第三步：vp=&values[3]-1=&values[2] *vp=0
//第四步：vp=&values[2]-1=&values[1] *vp=0
//第五步：vp=&values[1]-1=&values[0] *vp=0
//第六步：vp=&values[0]-1<&values[0] 跳出循环  vp最终指向values[0]前面的内存位置
//实际在绝大部分编译器上是可以顺利完成任务的，然而我们还是应该避免这样写，因为标准并不保证它可行
//标准规定：P86

//5️⃣指针和数组
int main()
{
    int arr[10]={0};
    int *p=arr;
    int sz=sizeof(arr)/sizeof(arr[0]);
    int i;
    for(i=0;i<sz;i++)
    {
        printf("%d ",*(p+i));//输出：0 0 0 0 0 0 0 0 0 0
        printf("%d ",p[i]);  //输出：0 0 0 0 0 0 0 0 0 0 
    }
    return 0;
}

//6️⃣二级指针
int main()
{
    int a=10;
    int* pa=&a;//pa是一个一级指针变量
    *pa=20;
    printf("%d\n",a);//输出：20
    int** ppa=&pa;//ppa是一个二级指针变量
    **ppa=30;
    printf("%d\n",a);//输出：30
    return 0;
}

//7️⃣指针数组
int main()
{
    int a=10;
    int b=20;
    int c=30;
    int *p[10]={&a,&b,&c}; //p就是存放指针的数组，为指针数组
    int i;
    for(i=0;i<3;i++)
    {
        printf("%d ",*(p[i]));//输出：10 20 30 
    }
    return 0;
}
//用指针数组模拟二维数组
int main()
{
    int ARR[3][4]={1,2,3,4,2,3,4,5,4,5,6,7}; //二维数组
    //1234
    //2345
    //4567
    int m,n;
    for(m=0;m<3;m++)
    {
        for(n=0;n<4;n++)
        {
            printf("%d",ARR[m][n]);
        }
        printf("\n");
    }
    //模拟二维数组
    int arr[4]={1,2,3,4}; //一维数组
    int brr[4]={2,3,4,5}; //一维数组
    int crr[4]={4,5,6,7}; //一维数组
    int *p[3]={arr,brr,crr}; //用指针数组把三个一维数组管理起来
    int i=0;
    int j=0;
    for(i=0;i<3;i++)
    {
        for(j=0;j<4;j++)
        {
            printf("%d",p[i][j]); //三个一维数组呈现二维数组的效果
        } //p[i][j]等价于*(p[i]+j)
        printf("\n");
    }
    return 0;
}
//输出：
//1234
//2345
//4567
//注意：只是模拟出二维数组的效果，实际上并非完全是二维数组，因为每一行空间并非是连续的。
int main()
{
   int arr[4]={1,2,3,4}; //一维数组
    int brr[4]={2,3,4,5}; //一维数组
    int crr[4]={4,5,6,7}; //一维数组
    int *p[3]={arr,brr,crr}; //用指针数组把三个一维数组管理起来
    printf("%p\n",arr);    //000000000061FE30
    printf("%p\n",p[0]+1); //000000000061FE34
    printf("%p\n",p);      //000000000061FDF0 
    printf("%p\n",p+1);    //000000000061FDF8
    return 0;
}